/***********************************************************************************
**  
* @copyright (c) 2010-2019, ChengDu Duyer Technology Co., LTD. All Right Reserved.
*
************************************************************************************/
/**
* @file	    mj_net_protocol.cpp
* @version     
* @brief      
* @author   duye
* @date	    2016-12-19
* @note 
*
* 1. 2016-12-19 duye Created this file
*/
#include <duye_logger.h>
#include <duye_xml.h>
#include <mj_net_protocol.h>

static const int8* DUYE_LOG_PREFIX = "mangjing.net_protocol";
static const int8* MSG_ROOT_NODE_NAME = "mangjing";
static const int8* PEER_MJS_NAME = "mangjings";
static const int8* PEER_MJC_NAME = "mangjingc";
static const int8* PEER_MJW_NAME = "mangjingw";

MJMsgAction::MJMsgAction() {}
MJMsgAction::~MJMsgAction() {}

void MJMsgAction::setName(const std::string& name) {
	m_act_name = name;
}

const std::string& MJMsgAction::getName() const {
	return m_act_name;
}

void MJMsgAction::setFrom(const std::string& from) {
	m_act_from = from;
}

const std::string& MJMsgAction::getFrom() const {
	return m_act_from;
}

void MJMsgAction::setTo(const std::string& to) {
	m_act_to = to;
}

const std::string& MJMsgAction::getTo() const {
	return m_act_to;
}

void MJMsgAction::addPara(const std::string& name, const std::string& value) {
	m_order_params.push_back(name);
	m_para_map.insert(std::make_pair(name, MJMsgParaNode(value)));
}

void MJMsgAction::addChildList(const std::string& name, const MJMsgAction::MJMsgParaList& list_data) {
	m_order_params.push_back(name);
	m_para_map.insert(std::make_pair(name, MJMsgParaNode(list_data)));
}

void MJMsgAction::addChildMap(const std::string& name, const MJMsgAction::MJMsgParaKeyVal& map_data) {
	m_order_params.push_back(name);
	m_para_map.insert(std::make_pair(name, MJMsgParaNode(map_data)));
}

std::string MJMsgAction::getValue(const std::string& name) {
	MJMsgParaMap::iterator iter = m_para_map.find(name);
	if (iter != m_para_map.end()) {
		return iter->second.value;
	}

	return "";
}

std::string MJMsgAction::getValue(const std::string& name) const {
	MJMsgParaMap::const_iterator iter = m_para_map.find(name);
	if (iter != m_para_map.end()) {
		return iter->second.value;
	}

	return "";
}

MJMsgAction::MJMsgParaNode* MJMsgAction::getParaNode(const std::string& name) {
	MJMsgParaMap::iterator iter = m_para_map.find(name);
	if (iter != m_para_map.end()) {
		return &iter->second;
	}

	return NULL;
}

const MJMsgAction::MJMsgParaNode* MJMsgAction::getParaNode(const std::string& name) const {
	MJMsgParaMap::const_iterator iter = m_para_map.find(name);
	if (iter != m_para_map.end()) {
		return &iter->second;
	}

	return NULL;
}

bool MJMsgAction::setValue(const std::string& name, const std::string& value) {
	MJMsgParaMap::iterator iter = m_para_map.find(name);
	if (iter == m_para_map.end()) {
		DUYE_ERROR("have no para:%s", name.c_str());
		return false;
	}

	iter->second.value = value;
	return true;
}

bool MJMsgAction::setChildList(const std::string& name, const MJMsgAction::MJMsgParaList& list_data) {
	MJMsgParaMap::iterator iter = m_para_map.find(name);
	if (iter == m_para_map.end()) {
		DUYE_ERROR("have no para:%s", name.c_str());
		return false;
	}

	iter->second.list_children = list_data;
	return true;
}

bool MJMsgAction::setChildMap(const std::string& name, const MJMsgAction::MJMsgParaKeyVal& map_data) {
	MJMsgParaMap::iterator iter = m_para_map.find(name);
	if (iter == m_para_map.end()) {
		DUYE_ERROR("have no para:%s", name.c_str());
		return false;
	}

	iter->second.map_children = map_data;
	return true;
}

const MJMsgAction::MJMsgParaMap& MJMsgAction::getParaMap() const {
	return m_para_map;
}

const std::list<std::string>& MJMsgAction::getOrderParams() const {
	return m_order_params;
}

MJMsgFactory::MJMsgFactory() : m_enable(false), m_peer_type(MJ_PEER_UNKNOWN) {
	m_peer_list.insert("mangjings");
	m_peer_list.insert("mangjingc");
	m_peer_list.insert("mangjingw");
}

MJMsgFactory::~MJMsgFactory() {}

MJMsgFactory& MJMsgFactory::ins() {
	static MJMsgFactory ins;
	return ins;
}

bool MJMsgFactory::init(const std::string& protocol_conf_file, const MJPeerType& type) {
    if (!m_xml_doc.loadFile(protocol_conf_file)) {
        DUYE_ERROR("load file '%s' failed:%s, line:%d", 
			protocol_conf_file.c_str(), m_xml_doc.errorDesc(), m_xml_doc.errorRow());
        return false;
    }

	duye::XmlElement* root = m_xml_doc.rootElement();
	if (!root) {
		DUYE_ERROR("root == NULL");
		return false;
	}

	if (strcmp(root->value(), MSG_ROOT_NODE_NAME) != 0) {
		DUYE_ERROR("root node name != %s", MSG_ROOT_NODE_NAME);
		return false;
	}

	duye::XmlElement* action = root->firstChildElement();
	if (!action) {
		DUYE_ERROR("root->firstChildElement() == NULL");
		return false;
	}

	while (action) {
		if (strcmp(action->value(), "action") != 0) {
			DUYE_ERROR("action node name != action");
			return false;
		}

		const int8* action_name = action->attribute("name");
		if (!action_name) {
			DUYE_ERROR("action_name == NULL");
			return false;
		}

		const int8* from = action->attribute("from");
		if (!from) {
			DUYE_ERROR("from == NULL");
			return false;
		}

		const int8* to = action->attribute("to");
		if (!to) {
			DUYE_ERROR("to == NULL");
			return false;
		}

		m_action_node_map.insert(std::make_pair(genNodeKey(action_name, from, to), action));
		action = action->nextSiblingElement();
	}

	if (type != MJ_PEER_SERVER && type != MJ_PEER_CLIENT) {
		DUYE_ERROR("input parameter 'type' error, should in(MJ_PEER_SERVER/MJ_PEER_CLIENT)");
		return false;
	}

	m_peer_type = type;
	if (m_peer_type == MJ_PEER_SERVER) {
		m_peer_name = PEER_MJS_NAME;
		m_to_peer_name = PEER_MJC_NAME;
	} else if (m_peer_type == MJ_PEER_CLIENT) {
		m_peer_name = PEER_MJC_NAME;
		m_to_peer_name = PEER_MJS_NAME;
	}

	m_enable = true;

	return true;
}

bool MJMsgFactory::fromatMsgXml(const int8* data, const uint32 len, std::list<MJMsgAction>& action_list) {
	if (!m_enable) {
		DUYE_ERROR("MJMsgFactory is disabled");
		return false;
	}

	duye::XmlDocument real_xml_doc;
    if (!real_xml_doc.load(data, len)) {
        DUYE_ERROR("load xml data failed:%s, line:%d", real_xml_doc.errorDesc(), real_xml_doc.errorRow());
        return false;
    }

 	duye::XmlElement* root = real_xml_doc.rootElement();
	if (!root) {
		DUYE_ERROR("root == NULL");
		return false;
	}

	if (strcmp(root->value(), MSG_ROOT_NODE_NAME) != 0) {
		DUYE_ERROR("root node name != %s", MSG_ROOT_NODE_NAME);
		return false;
	}

	duye::XmlElement* action_node = root->firstChildElement();
	if (!action_node) {
		DUYE_ERROR("root->firstChildElement() == NULL");
		return false;
	}

	bool flage = true;
	while (action_node) {
		if (strcmp(action_node->value(), "action")) {
			DUYE_ERROR("action node name != action");
			flage = false;
			return false;
		}

		const int8* action_name = action_node->attribute("name");
		if (!action_name) {
			DUYE_ERROR("action_name == NULL");
			flage = false;
			break;
		}

		const int8* from = action_node->attribute("from");
		if (!from) {
			DUYE_ERROR("action node 'from' == NULL");
			flage = false;
			return false;
		}

		std::set<std::string>::iterator from_iter = m_peer_list.find(from);
		if (from_iter == m_peer_list.end()) {
			DUYE_ERROR("msg from attribute is '%s', error, should in(mangjingc/mangjingw/mangjings)", from);
			return false;
		}

		const int8* to = action_node->attribute("to");
		if (!to) {
			DUYE_ERROR("action node 'to' == NULL");
			flage = false;
			return false;
		}

		std::set<std::string>::iterator to_iter = m_peer_list.find(to);
		if (to_iter == m_peer_list.end()) {
			DUYE_ERROR("msg to attribute is '%s', error, should in(mangjingc/mangjingw/mangjings)", to);
			return false;
		}		

		duye::XmlElement* query_action = queryActionNode(action_name, from, to);
		if (!query_action) {
			DUYE_ERROR("have not action:%s in config mangjing_msg.xml", action_name);
			flage = false;
			break;
		}

		duye::XmlElement* param_node = action_node->firstChildElement();
		if (!param_node) {
			DUYE_ERROR("action->firstChildElement() == NULL");
			flage = false;
			break;
		}

		MJMsgAction action;
		action.setName(action_name);
		action.setFrom(from);
		action.setTo(to);

		while (param_node) {
			if (strcmp(param_node->value(), "param") != 0) {
				DUYE_ERROR("param node name != param");
				flage = false;
				break;
			}

			const int8* param_name = param_node->attribute("name");
			if (!param_name) {
				DUYE_ERROR("param node attri 'name' == NULL");
				flage = false;
				break;
			}

			if (!queryActionPara(query_action, param_name)) {
				DUYE_ERROR("query param 'name=%s' not exist", param_name);
				flage = false;
				break;
			}

			const int8* param_type = param_node->attribute("type");
			if (!param_type) {
				const int8* param_value = param_node->attribute("value");
				if (!param_value) {
					DUYE_ERROR("param node attri 'value' == NULL");
					flage = false;
					break;
				}
				action.addPara(param_name, param_value);
			} else if (strcmp(param_type, "list") == 0) {
				MJMsgAction::MJMsgParaList list_data;
				duye::XmlElement* list_children = param_node->firstChildElement();
				while (list_children) {
					const int8* text = list_children->getText();
					if (!text) {
						DUYE_ERROR("list_children get text == NULL");
						break;
					}

					list_data.push_back(text);
					list_children = list_children->nextSiblingElement();
				}

				action.addChildList(param_name, list_data);
			} else if (strcmp(param_type, "map") == 0) {
				MJMsgAction::MJMsgParaKeyVal map_data;
				duye::XmlElement* map_children = param_node->firstChildElement();
				while (map_children) {
					const int8* c_name = map_children->attribute("name");
					if (!c_name) {
						DUYE_ERROR("map_children get para_name == NULL");
						break;
					}

					const int8* c_value = map_children->attribute("value");
					if (!c_value) {
						DUYE_ERROR("map_children get para_value == NULL");
						break;
					}					

					map_data.insert(std::make_pair(c_name, c_value));
					map_children = map_children->nextSiblingElement();
				}

				action.addChildMap(param_name, map_data);
			} else {
				DUYE_ERROR("param node attri 'type' == %s, error", param_type);
				flage = false;
				break;			
			}

			param_node = param_node->nextSiblingElement();
		}

		if (!flage) {
			break;
		}

		action_list.push_back(action);
		action_node = action_node->nextSiblingElement();
	}

	if (!flage) {
		action_list.clear();
	}

	return flage;
}

bool MJMsgFactory::createAction(const std::string& act_name, MJMsgAction& action, const MJPeerType type) {
	if (!m_enable) {
		DUYE_ERROR("MJMsgFactory is disabled");
		return false;
	}

	std::string to_peer = m_to_peer_name;
	if (type != MJ_PEER_UNKNOWN) {
		to_peer = getPeerName(type);
		if (to_peer.empty()) {
			DUYE_ERROR("peer type(%d) is unknown", type);
			return false;
		}
	}

	duye::XmlElement* action_node = queryActionNode(act_name.c_str(), m_peer_name.c_str(), to_peer.c_str());
	if (!action_node) {
		DUYE_ERROR("action_node == NULL");
		return false;
	}

	action.setName(act_name);
	action.setFrom(m_peer_name);
	action.setTo(to_peer);

	bool flage = true;
	duye::XmlElement* para_node = action_node->firstChildElement();
	while (para_node) {
		if (strcmp(para_node->value(), "param") != 0) {
			DUYE_ERROR("param node name != param");
			flage = false;
			break;
		}

		const int8* para_name = para_node->attribute("name");
		if (!para_name) {
			DUYE_ERROR("para_name == NULL");
			flage = false;
			break;
		}

		action.addPara(para_name, "");
		para_node = para_node->nextSiblingElement();
	}

	return flage;
}

bool MJMsgFactory::createMsgXml(const MJMsgAction& action, std::string& msg_xml) {
	if (!m_enable) {
		DUYE_ERROR("MJMsgFactory is disabled");
		return false;
	}

	msg_xml.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
	msg_xml.append("\r\n");
	msg_xml.append("<" + std::string(MSG_ROOT_NODE_NAME) + ">");
	msg_xml.append("\r\n");
	joinAction(action, msg_xml);
	msg_xml.append("</" + std::string(MSG_ROOT_NODE_NAME) + ">");
	msg_xml.append("\r\n");

	return true;
}

bool MJMsgFactory::createMsgXml(const std::list<MJMsgAction>& action_list, std::string& msg_xml) {
	if (!m_enable) {
		DUYE_ERROR("MJMsgFactory is disabled");
		return false;
	}

	msg_xml.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
	msg_xml.append("\r\n");
	msg_xml.append("<" + std::string(MSG_ROOT_NODE_NAME) + ">");
	msg_xml.append("\r\n");

	std::list<MJMsgAction>::const_iterator iter = action_list.begin();
	for (; iter != action_list.end(); ++iter) {
		joinAction(*iter, msg_xml);
	}

	msg_xml.append("</" + std::string(MSG_ROOT_NODE_NAME) + ">");
	msg_xml.append("\r\n");

	return true;
}

duye::XmlElement* MJMsgFactory::queryActionNode(const int8* action_name, const int8* from, const int8* to) {
	MJMsgActionNodeMap::iterator iter = m_action_node_map.find(genNodeKey(action_name, from, to));
	if (iter == m_action_node_map.end()) {
		DUYE_ERROR("don't found action(key=%s) from m_action_node_map", genNodeKey(action_name, from, to).c_str());
		return NULL;
	}

	duye::XmlElement* action = iter->second;
	if (!action) {
		DUYE_ERROR("iter->second == NULL");
		return NULL;
	}

	return action;
}

bool MJMsgFactory::queryActionPara(duye::XmlElement* action_node, const int8* para_name) {
	if (!action_node || !para_name) {
		return false;
	}

	duye::XmlElement* pram = action_node->firstChildElement();
	if (!pram) {
		return false;
	}

	bool flage = false;
	while (pram) {
		const int8* i_para_name = pram->attribute("name");
		if (!i_para_name) {
			break;
		}

		if (strcmp(i_para_name, para_name) == 0) {
			flage = true;
			break;
		}

		pram = pram->nextSiblingElement();
	}

	return flage;
}

/*
<action name="heartbeat">
	<param name="ip" value="192.168.2.1" />
</action>	
*/
void MJMsgFactory::joinAction(const MJMsgAction& action, std::string& msg_xml) {
	msg_xml.append("  <action name=\"" + action.getName() + "\" from=\"" + action.getFrom() + "\" to=\"" + action.getTo() + "\">");
	msg_xml.append("\r\n");

	const MJMsgAction::MJMsgParaMap& msg_para_map = action.getParaMap();
	const std::list<std::string>& order_params = action.getOrderParams();
	std::list<std::string>::const_iterator iter = order_params.begin();
	for (; iter != order_params.end(); ++iter) {
		const std::string& name = *iter;
		const MJMsgAction::MJMsgParaNode& para_node = msg_para_map.find(name)->second;
		if (para_node.type.empty()) {
			msg_xml.append("    <param name=\"" + name + "\" value=\"" + para_node.value + "\" />");
			msg_xml.append("\r\n");
		} else if (para_node.type == "list") {
			msg_xml.append("    <param name=\"" + name + "\" type=\"list\">");
			msg_xml.append("\r\n");			
			MJMsgAction::MJMsgParaList::const_iterator list_iter = para_node.list_children.begin();
			for (; list_iter != para_node.list_children.end(); ++list_iter) {
				msg_xml.append("      <param>" + *list_iter + "</param>");
				msg_xml.append("\r\n");
			}
			msg_xml.append("    <param>\r\n");
		} else if (para_node.type == "map") {
			msg_xml.append("    <param name=\"" + name + "\" type=\"map\">");
			msg_xml.append("\r\n");			
			MJMsgAction::MJMsgParaKeyVal::const_iterator map_iter = para_node.map_children.begin();
			for (; map_iter != para_node.map_children.end(); ++map_iter) {
				msg_xml.append("      <param name=\"" + map_iter->first + "\" value=\"" + map_iter->second + "\" />");
				msg_xml.append("\r\n");	
			}
			msg_xml.append("    <param>\r\n");
		} else {
			DUYE_ERROR("action para_node.type = %s, error", para_node.type.c_str());
		}
	}

	msg_xml.append("  </action>");
	msg_xml.append("\r\n");
}

std::string MJMsgFactory::genNodeKey(const std::string& act_name, const std::string& act_from, const std::string& act_to) {
	return act_name + "_" + act_from + "_" + act_to;
}

std::string MJMsgFactory::getPeerName(const MJPeerType type) {
	switch (type) {
		case MJ_PEER_SERVER:
			return PEER_MJS_NAME;
		case MJ_PEER_CLIENT:
			return PEER_MJC_NAME;
		case MJ_PEER_WEB:
			return PEER_MJW_NAME;
		default:
			return "";
	}

	return "";
}